#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
二进制文件处理器

该模块提供了二进制文件的读写和处理功能。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import os
import io
import struct
import binascii
import hashlib
import mmap
from typing import Optional, Union, Dict, Any, List, Tuple, BinaryIO, Callable, Iterator
from pathlib import Path

from modules.logger import LogManager
from modules.exception import exception_handler, FileException
from modules.fileio.file_manager import FileManager


class BinaryFileHandler:
    """
    二进制文件处理器
    
    该类提供了二进制文件的读写和处理功能。
    """
    
    def __init__(self, file_manager: Optional[FileManager] = None, logger=None):
        """
        初始化二进制文件处理器
        
        Args:
            file_manager: 文件管理器实例，如果为None则创建新实例
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.file_manager = file_manager or FileManager()
        self.logger = logger or LogManager.get_both_logger(name="binary_handler")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read(self, file_path: Union[str, Path]) -> bytes:
        """
        读取二进制文件
        
        Args:
            file_path: 二进制文件路径
        
        Returns:
            bytes: 文件内容
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> data = binary_handler.read('example.bin')
            >>> print(f"读取了{len(data)}字节")
        """
        return self.file_manager.read_bytes(file_path)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_chunk(self, file_path: Union[str, Path], offset: int = 0, size: int = None) -> bytes:
        """
        读取二进制文件的一部分
        
        Args:
            file_path: 二进制文件路径
            offset: 起始偏移量，默认为0
            size: 要读取的字节数，如果为None则读取到文件末尾
        
        Returns:
            bytes: 读取的数据
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 读取文件的前100个字节
            >>> data = binary_handler.read_chunk('example.bin', 0, 100)
            >>> print(f"读取了{len(data)}字节")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            with open(file_path, 'rb') as f:
                f.seek(offset)
                if size is None:
                    data = f.read()
                else:
                    data = f.read(size)
        except Exception as e:
            raise FileException(f"读取文件块失败: {e}")
        
        self.logger.debug(f"已读取文件块: {file_path}, 偏移量: {offset}, 大小: {len(data)}字节")
        return data
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write(self, file_path: Union[str, Path], data: bytes, create_dirs: bool = True) -> bool:
        """
        写入数据到二进制文件
        
        Args:
            file_path: 二进制文件路径
            data: 要写入的数据
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> binary_handler.write('example.bin', b'\x00\x01\x02\x03')
        """
        return self.file_manager.write_bytes(file_path, data, create_dirs=create_dirs)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def append(self, file_path: Union[str, Path], data: bytes, 
              create_if_not_exists: bool = True, create_dirs: bool = True) -> bool:
        """
        追加数据到二进制文件
        
        Args:
            file_path: 二进制文件路径
            data: 要追加的数据
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 追加成功返回True
        
        Raises:
            FileException: 追加失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> binary_handler.append('example.bin', b'\x04\x05\x06')
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists() and not create_if_not_exists:
            raise FileException(f"文件不存在: {file_path}")
        
        # 确保父目录存在
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with open(file_path, 'ab') as f:
                f.write(data)
        except Exception as e:
            raise FileException(f"追加数据失败: {e}")
        
        self.logger.debug(f"已追加{len(data)}字节到二进制文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_at(self, file_path: Union[str, Path], data: bytes, offset: int, 
               create_if_not_exists: bool = True, create_dirs: bool = True) -> bool:
        """
        在指定位置写入数据
        
        Args:
            file_path: 二进制文件路径
            data: 要写入的数据
            offset: 写入位置的偏移量
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 在偏移量为100的位置写入数据
            >>> binary_handler.write_at('example.bin', b'\x07\x08\x09', 100)
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists():
            if not create_if_not_exists:
                raise FileException(f"文件不存在: {file_path}")
            # 创建空文件
            if create_dirs:
                file_path.parent.mkdir(parents=True, exist_ok=True)
            with open(file_path, 'wb') as f:
                pass
        
        try:
            with open(file_path, 'r+b') as f:
                # 如果偏移量超过文件大小，先填充零字节
                file_size = f.seek(0, os.SEEK_END)
                if offset > file_size:
                    f.write(b'\x00' * (offset - file_size))
                
                # 在指定位置写入数据
                f.seek(offset)
                f.write(data)
        except Exception as e:
            raise FileException(f"在指定位置写入数据失败: {e}")
        
        self.logger.debug(f"已在偏移量{offset}处写入{len(data)}字节: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def copy(self, source_path: Union[str, Path], target_path: Union[str, Path], 
            buffer_size: int = 1024 * 1024, create_dirs: bool = True) -> bool:
        """
        复制二进制文件
        
        Args:
            source_path: 源文件路径
            target_path: 目标文件路径
            buffer_size: 缓冲区大小，默认为1MB
            create_dirs: 如果目标文件的父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 复制成功返回True
        
        Raises:
            FileException: 源文件不存在或复制失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> binary_handler.copy('source.bin', 'target.bin')
        """
        source_path = Path(source_path)
        target_path = Path(target_path)
        
        if not source_path.exists():
            raise FileException(f"源文件不存在: {source_path}")
        
        # 确保目标文件的父目录存在
        if create_dirs:
            target_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with open(source_path, 'rb') as src, open(target_path, 'wb') as dst:
                while True:
                    buffer = src.read(buffer_size)
                    if not buffer:
                        break
                    dst.write(buffer)
        except Exception as e:
            raise FileException(f"复制文件失败: {e}")
        
        self.logger.debug(f"已复制二进制文件: {source_path} -> {target_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def hexdump(self, file_path: Union[str, Path], offset: int = 0, size: int = None, 
              bytes_per_line: int = 16, show_ascii: bool = True) -> str:
        """
        生成二进制文件的十六进制转储
        
        Args:
            file_path: 二进制文件路径
            offset: 起始偏移量，默认为0
            size: 要转储的字节数，如果为None则转储到文件末尾
            bytes_per_line: 每行显示的字节数，默认为16
            show_ascii: 是否显示ASCII表示，默认为True
        
        Returns:
            str: 十六进制转储字符串
        
        Raises:
            FileException: 文件不存在或转储失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> dump = binary_handler.hexdump('example.bin', 0, 64)
            >>> print(dump)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 读取数据
            data = self.read_chunk(file_path, offset, size)
            
            # 生成十六进制转储
            result = []
            for i in range(0, len(data), bytes_per_line):
                chunk = data[i:i + bytes_per_line]
                hex_line = ' '.join(f'{b:02x}' for b in chunk)
                
                # 填充空格以对齐ASCII部分
                if len(chunk) < bytes_per_line:
                    hex_line += ' ' * (3 * (bytes_per_line - len(chunk)))
                
                # 添加ASCII表示
                if show_ascii:
                    ascii_line = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
                    line = f'{offset + i:08x}:  {hex_line}  |{ascii_line}|'
                else:
                    line = f'{offset + i:08x}:  {hex_line}'
                
                result.append(line)
        except Exception as e:
            raise FileException(f"生成十六进制转储失败: {e}")
        
        return '\n'.join(result)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def to_hex_string(self, file_path: Union[str, Path]) -> str:
        """
        将二进制文件转换为十六进制字符串
        
        Args:
            file_path: 二进制文件路径
        
        Returns:
            str: 十六进制字符串
        
        Raises:
            FileException: 文件不存在或转换失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> hex_str = binary_handler.to_hex_string('example.bin')
            >>> print(hex_str)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            data = self.read(file_path)
            hex_string = binascii.hexlify(data).decode('ascii')
        except Exception as e:
            raise FileException(f"转换为十六进制字符串失败: {e}")
        
        return hex_string
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def from_hex_string(self, hex_string: str, file_path: Union[str, Path], 
                      create_dirs: bool = True) -> bool:
        """
        将十六进制字符串写入二进制文件
        
        Args:
            hex_string: 十六进制字符串
            file_path: 二进制文件路径
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> binary_handler.from_hex_string('0001020304', 'example.bin')
        """
        file_path = Path(file_path)
        
        try:
            data = binascii.unhexlify(hex_string)
            return self.write(file_path, data, create_dirs=create_dirs)
        except Exception as e:
            raise FileException(f"从十六进制字符串写入失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def to_base64(self, file_path: Union[str, Path]) -> str:
        """
        将二进制文件转换为Base64字符串
        
        Args:
            file_path: 二进制文件路径
        
        Returns:
            str: Base64字符串
        
        Raises:
            FileException: 文件不存在或转换失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> base64_str = binary_handler.to_base64('example.bin')
            >>> print(base64_str)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            data = self.read(file_path)
            base64_string = binascii.b2a_base64(data, newline=False).decode('ascii')
        except Exception as e:
            raise FileException(f"转换为Base64字符串失败: {e}")
        
        return base64_string
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def from_base64(self, base64_string: str, file_path: Union[str, Path], 
                  create_dirs: bool = True) -> bool:
        """
        将Base64字符串写入二进制文件
        
        Args:
            base64_string: Base64字符串
            file_path: 二进制文件路径
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> binary_handler.from_base64('AAECAwQ=', 'example.bin')
        """
        file_path = Path(file_path)
        
        try:
            data = binascii.a2b_base64(base64_string)
            return self.write(file_path, data, create_dirs=create_dirs)
        except Exception as e:
            raise FileException(f"从Base64字符串写入失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def compute_hash(self, file_path: Union[str, Path], algorithm: str = 'sha256', 
                   buffer_size: int = 1024 * 1024) -> str:
        """
        计算二进制文件的哈希值
        
        Args:
            file_path: 二进制文件路径
            algorithm: 哈希算法，可选'md5', 'sha1', 'sha256', 'sha512'等，默认为'sha256'
            buffer_size: 缓冲区大小，默认为1MB
        
        Returns:
            str: 哈希值的十六进制字符串
        
        Raises:
            FileException: 文件不存在或计算失败时抛出
            ValueError: 算法不支持时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> md5 = binary_handler.compute_hash('example.bin', 'md5')
            >>> print(f"MD5: {md5}")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 创建哈希对象
            hash_obj = hashlib.new(algorithm)
            
            # 分块读取文件并更新哈希
            with open(file_path, 'rb') as f:
                while True:
                    data = f.read(buffer_size)
                    if not data:
                        break
                    hash_obj.update(data)
            
            # 获取哈希值
            hash_value = hash_obj.hexdigest()
        except ValueError as e:
            raise ValueError(f"不支持的哈希算法: {algorithm}")
        except Exception as e:
            raise FileException(f"计算哈希值失败: {e}")
        
        self.logger.debug(f"已计算文件的{algorithm}哈希值: {file_path}")
        return hash_value
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def compare_files(self, file_path1: Union[str, Path], file_path2: Union[str, Path], 
                    buffer_size: int = 1024 * 1024) -> bool:
        """
        比较两个二进制文件是否相同
        
        Args:
            file_path1: 第一个文件路径
            file_path2: 第二个文件路径
            buffer_size: 缓冲区大小，默认为1MB
        
        Returns:
            bool: 如果文件内容相同则返回True，否则返回False
        
        Raises:
            FileException: 文件不存在或比较失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> is_same = binary_handler.compare_files('file1.bin', 'file2.bin')
            >>> print(f"文件内容{'相同' if is_same else '不同'}")
        """
        file_path1 = Path(file_path1)
        file_path2 = Path(file_path2)
        
        if not file_path1.exists():
            raise FileException(f"文件不存在: {file_path1}")
        
        if not file_path2.exists():
            raise FileException(f"文件不存在: {file_path2}")
        
        # 如果文件大小不同，则文件内容一定不同
        if file_path1.stat().st_size != file_path2.stat().st_size:
            return False
        
        try:
            with open(file_path1, 'rb') as f1, open(file_path2, 'rb') as f2:
                while True:
                    buffer1 = f1.read(buffer_size)
                    buffer2 = f2.read(buffer_size)
                    
                    if buffer1 != buffer2:
                        return False
                    
                    if not buffer1:  # 文件读取完毕
                        break
        except Exception as e:
            raise FileException(f"比较文件失败: {e}")
        
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def find_differences(self, file_path1: Union[str, Path], file_path2: Union[str, Path], 
                       max_differences: int = 10) -> List[Tuple[int, bytes, bytes]]:
        """
        查找两个二进制文件的差异
        
        Args:
            file_path1: 第一个文件路径
            file_path2: 第二个文件路径
            max_differences: 最多返回的差异数，默认为10
        
        Returns:
            List[Tuple[int, bytes, bytes]]: 差异列表，每个元素为(偏移量, 文件1的字节, 文件2的字节)
        
        Raises:
            FileException: 文件不存在或查找失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> diffs = binary_handler.find_differences('file1.bin', 'file2.bin')
            >>> for offset, bytes1, bytes2 in diffs:
            >>>     print(f"偏移量: {offset}, 文件1: {bytes1.hex()}, 文件2: {bytes2.hex()}")
        """
        file_path1 = Path(file_path1)
        file_path2 = Path(file_path2)
        
        if not file_path1.exists():
            raise FileException(f"文件不存在: {file_path1}")
        
        if not file_path2.exists():
            raise FileException(f"文件不存在: {file_path2}")
        
        differences = []
        
        try:
            with open(file_path1, 'rb') as f1, open(file_path2, 'rb') as f2:
                offset = 0
                while True:
                    byte1 = f1.read(1)
                    byte2 = f2.read(1)
                    
                    # 如果其中一个文件已经读取完毕
                    if not byte1 or not byte2:
                        if byte1:
                            differences.append((offset, byte1, b''))
                        elif byte2:
                            differences.append((offset, b'', byte2))
                        break
                    
                    # 比较字节
                    if byte1 != byte2:
                        differences.append((offset, byte1, byte2))
                        if len(differences) >= max_differences:
                            break
                    
                    offset += 1
        except Exception as e:
            raise FileException(f"查找差异失败: {e}")
        
        return differences
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def search_bytes(self, file_path: Union[str, Path], pattern: bytes, 
                   buffer_size: int = 1024 * 1024) -> List[int]:
        """
        在二进制文件中搜索字节模式
        
        Args:
            file_path: 二进制文件路径
            pattern: 要搜索的字节模式
            buffer_size: 缓冲区大小，默认为1MB
        
        Returns:
            List[int]: 匹配的偏移量列表
        
        Raises:
            FileException: 文件不存在或搜索失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> offsets = binary_handler.search_bytes('example.bin', b'\x00\x01\x02')
            >>> print(f"找到{len(offsets)}个匹配")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        if not pattern:
            raise ValueError("搜索模式不能为空")
        
        matches = []
        pattern_len = len(pattern)
        
        try:
            with open(file_path, 'rb') as f:
                file_size = file_path.stat().st_size
                
                # 如果文件大小小于缓冲区大小，直接读取整个文件
                if file_size <= buffer_size:
                    data = f.read()
                    offset = 0
                    while True:
                        pos = data.find(pattern, offset)
                        if pos == -1:
                            break
                        matches.append(pos)
                        offset = pos + 1
                else:
                    # 分块读取文件，确保模式不会跨越缓冲区边界而被遗漏
                    overlap = pattern_len - 1
                    offset = 0
                    prev_data = b''
                    
                    while offset < file_size:
                        f.seek(offset)
                        data = f.read(buffer_size)
                        if not data:
                            break
                        
                        # 将前一个缓冲区的末尾与当前缓冲区合并，以处理跨越边界的模式
                        combined_data = prev_data + data
                        
                        # 在合并数据中搜索模式
                        pos = 0
                        while True:
                            pos = combined_data.find(pattern, pos)
                            if pos == -1:
                                break
                            
                            # 计算实际偏移量
                            actual_offset = offset - len(prev_data) + pos
                            if actual_offset >= 0:  # 忽略负偏移量
                                matches.append(actual_offset)
                            
                            pos += 1
                        
                        # 保存当前缓冲区的末尾，用于下一次迭代
                        prev_data = data[-overlap:] if len(data) >= overlap else data
                        offset += len(data) - overlap
        except Exception as e:
            raise FileException(f"搜索字节模式失败: {e}")
        
        return matches
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def replace_bytes(self, file_path: Union[str, Path], pattern: bytes, replacement: bytes) -> int:
        """
        在二进制文件中替换字节模式
        
        Args:
            file_path: 二进制文件路径
            pattern: 要搜索的字节模式
            replacement: 替换为的字节
        
        Returns:
            int: 替换次数
        
        Raises:
            FileException: 文件不存在或替换失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> count = binary_handler.replace_bytes('example.bin', b'\x00\x01', b'\xFF\xFE')
            >>> print(f"替换了{count}处")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        if not pattern:
            raise ValueError("搜索模式不能为空")
        
        try:
            # 读取整个文件
            with open(file_path, 'rb') as f:
                data = f.read()
            
            # 替换字节模式
            new_data, count = self._replace_all(data, pattern, replacement)
            
            # 如果有替换，写回文件
            if count > 0:
                with open(file_path, 'wb') as f:
                    f.write(new_data)
        except Exception as e:
            raise FileException(f"替换字节模式失败: {e}")
        
        self.logger.debug(f"已在二进制文件中替换字节模式，共{count}处: {file_path}")
        return count
    
    def _replace_all(self, data: bytes, pattern: bytes, replacement: bytes) -> Tuple[bytes, int]:
        """
        替换字节数据中的所有模式
        
        Args:
            data: 原始字节数据
            pattern: 要搜索的字节模式
            replacement: 替换为的字节
        
        Returns:
            Tuple[bytes, int]: (替换后的数据, 替换次数)
        """
        if len(pattern) == len(replacement):
            # 如果模式和替换的长度相同，可以直接替换
            count = 0
            result = bytearray(data)
            pos = 0
            
            while True:
                pos = data.find(pattern, pos)
                if pos == -1:
                    break
                
                for i in range(len(replacement)):
                    result[pos + i] = replacement[i]
                
                pos += len(pattern)
                count += 1
            
            return bytes(result), count
        else:
            # 如果长度不同，需要重建数据
            parts = data.split(pattern)
            count = len(parts) - 1
            result = replacement.join(parts)
            return result, count
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_struct(self, file_path: Union[str, Path], format_str: str, offset: int = 0) -> Tuple:
        """
        从二进制文件中读取结构化数据
        
        Args:
            file_path: 二进制文件路径
            format_str: 结构格式字符串，如'<II'表示两个小端无符号整数
            offset: 读取位置的偏移量，默认为0
        
        Returns:
            Tuple: 解包后的数据元组
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 读取两个32位整数
            >>> values = binary_handler.read_struct('example.bin', '<II')
            >>> print(f"读取的值: {values}")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 计算需要读取的字节数
            size = struct.calcsize(format_str)
            
            # 读取数据
            with open(file_path, 'rb') as f:
                f.seek(offset)
                data = f.read(size)
                
                if len(data) < size:
                    raise FileException(f"数据不足，需要{size}字节，但只读取到{len(data)}字节")
                
                # 解包数据
                values = struct.unpack(format_str, data)
        except struct.error as e:
            raise FileException(f"解析结构化数据失败: {e}")
        except Exception as e:
            raise FileException(f"读取结构化数据失败: {e}")
        
        return values
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_struct(self, file_path: Union[str, Path], format_str: str, values: Tuple, 
                   offset: int = None, create_if_not_exists: bool = True, 
                   create_dirs: bool = True) -> bool:
        """
        将结构化数据写入二进制文件
        
        Args:
            file_path: 二进制文件路径
            format_str: 结构格式字符串，如'<II'表示两个小端无符号整数
            values: 要写入的值元组
            offset: 写入位置的偏移量，如果为None则追加到文件末尾
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 写入两个32位整数
            >>> binary_handler.write_struct('example.bin', '<II', (42, 99))
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists():
            if not create_if_not_exists:
                raise FileException(f"文件不存在: {file_path}")
            # 创建空文件
            if create_dirs:
                file_path.parent.mkdir(parents=True, exist_ok=True)
            with open(file_path, 'wb') as f:
                pass
        
        try:
            # 打包数据
            data = struct.pack(format_str, *values)
            
            # 写入数据
            if offset is None:
                # 追加到文件末尾
                return self.append(file_path, data, create_if_not_exists=False)
            else:
                # 在指定位置写入
                return self.write_at(file_path, data, offset, create_if_not_exists=False)
        except struct.error as e:
            raise FileException(f"打包结构化数据失败: {e}")
        except Exception as e:
            raise FileException(f"写入结构化数据失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def memory_map(self, file_path: Union[str, Path], access: str = 'r') -> mmap.mmap:
        """
        创建文件的内存映射
        
        Args:
            file_path: 二进制文件路径
            access: 访问模式，'r'表示只读，'w'表示读写，默认为'r'
        
        Returns:
            mmap.mmap: 内存映射对象
        
        Raises:
            FileException: 文件不存在或映射失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 创建只读内存映射
            >>> with binary_handler.memory_map('example.bin') as mm:
            >>>     data = mm[:100]  # 读取前100个字节
            >>>     print(f"读取了{len(data)}字节")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 确定访问模式
            if access == 'r':
                mode = 'rb'
                mmap_access = mmap.ACCESS_READ
            elif access == 'w':
                mode = 'r+b'
                mmap_access = mmap.ACCESS_WRITE
            else:
                raise ValueError(f"不支持的访问模式: {access}，只支持'r'或'w'")
            
            # 打开文件并创建内存映射
            file_obj = open(file_path, mode)
            try:
                # 如果文件大小为0，无法创建内存映射
                if file_path.stat().st_size == 0:
                    file_obj.close()
                    raise FileException(f"无法映射空文件: {file_path}")
                
                mm = mmap.mmap(file_obj.fileno(), 0, access=mmap_access)
                # 注意：关闭文件对象不会影响内存映射
                file_obj.close()
                return mm
            except Exception as e:
                file_obj.close()
                raise e
        except Exception as e:
            raise FileException(f"创建内存映射失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def split_file(self, file_path: Union[str, Path], output_dir: Union[str, Path], 
                 chunk_size: int, create_dirs: bool = True) -> List[Path]:
        """
        将二进制文件拆分为多个小文件
        
        Args:
            file_path: 二进制文件路径
            output_dir: 输出目录
            chunk_size: 每个文件的字节数
            create_dirs: 如果输出目录不存在，是否创建，默认为True
        
        Returns:
            List[Path]: 生成的文件路径列表
        
        Raises:
            FileException: 文件不存在或拆分失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 将文件拆分为每个1MB的小文件
            >>> files = binary_handler.split_file('large.bin', 'output', 1024 * 1024)
            >>> print(f"拆分为{len(files)}个文件")
        """
        file_path = Path(file_path)
        output_dir = Path(output_dir)
        
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        if chunk_size <= 0:
            raise ValueError(f"每个文件的字节数必须大于0: {chunk_size}")
        
        # 确保输出目录存在
        if create_dirs:
            output_dir.mkdir(parents=True, exist_ok=True)
        
        output_files = []
        
        try:
            with open(file_path, 'rb') as f:
                file_count = 0
                
                while True:
                    data = f.read(chunk_size)
                    if not data:
                        break
                    
                    file_count += 1
                    output_file = output_dir / f"{file_path.stem}_{file_count:03d}{file_path.suffix}"
                    
                    with open(output_file, 'wb') as out_f:
                        out_f.write(data)
                    
                    output_files.append(output_file)
        except Exception as e:
            raise FileException(f"拆分文件失败: {e}")
        
        self.logger.debug(f"已将文件拆分为{len(output_files)}个小文件: {file_path}")
        return output_files
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def merge_files(self, file_paths: List[Union[str, Path]], output_path: Union[str, Path], 
                  buffer_size: int = 1024 * 1024, create_dirs: bool = True) -> bool:
        """
        合并多个二进制文件
        
        Args:
            file_paths: 要合并的文件路径列表
            output_path: 输出文件路径
            buffer_size: 缓冲区大小，默认为1MB
            create_dirs: 如果输出文件的父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 合并成功返回True
        
        Raises:
            FileException: 合并失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> binary_handler.merge_files(['part_1.bin', 'part_2.bin'], 'merged.bin')
        """
        output_path = Path(output_path)
        
        # 确保输出文件的父目录存在
        if create_dirs:
            output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with open(output_path, 'wb') as out_file:
                for file_path in file_paths:
                    file_path = Path(file_path)
                    if not file_path.exists():
                        self.logger.warning(f"文件不存在，跳过: {file_path}")
                        continue
                    
                    with open(file_path, 'rb') as in_file:
                        while True:
                            buffer = in_file.read(buffer_size)
                            if not buffer:
                                break
                            out_file.write(buffer)
        except Exception as e:
            raise FileException(f"合并文件失败: {e}")
        
        self.logger.debug(f"已合并{len(file_paths)}个文件到: {output_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def create_sparse_file(self, file_path: Union[str, Path], size: int, 
                         create_dirs: bool = True) -> bool:
        """
        创建稀疏文件（逻辑大小大于物理大小的文件）
        
        Args:
            file_path: 文件路径
            size: 文件大小（字节）
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 创建成功返回True
        
        Raises:
            FileException: 创建失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 创建一个10GB的稀疏文件
            >>> binary_handler.create_sparse_file('sparse.bin', 10 * 1024 * 1024 * 1024)
        """
        file_path = Path(file_path)
        
        # 确保父目录存在
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with open(file_path, 'wb') as f:
                # 在Windows上，可以使用seek和写入一个字节来创建稀疏文件
                f.seek(size - 1)
                f.write(b'\x00')
        except Exception as e:
            raise FileException(f"创建稀疏文件失败: {e}")
        
        self.logger.debug(f"已创建大小为{size}字节的稀疏文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def truncate(self, file_path: Union[str, Path], size: int) -> bool:
        """
        截断或扩展文件到指定大小
        
        Args:
            file_path: 文件路径
            size: 目标大小（字节）
        
        Returns:
            bool: 操作成功返回True
        
        Raises:
            FileException: 文件不存在或操作失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 截断文件到1KB
            >>> binary_handler.truncate('example.bin', 1024)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            with open(file_path, 'r+b') as f:
                f.truncate(size)
        except Exception as e:
            raise FileException(f"截断文件失败: {e}")
        
        self.logger.debug(f"已将文件截断或扩展到{size}字节: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def extract_chunk(self, file_path: Union[str, Path], output_path: Union[str, Path], 
                    start: int, size: int, create_dirs: bool = True) -> bool:
        """
        从文件中提取一块数据并保存到新文件
        
        Args:
            file_path: 源文件路径
            output_path: 输出文件路径
            start: 起始偏移量
            size: 要提取的字节数
            create_dirs: 如果输出文件的父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 提取成功返回True
        
        Raises:
            FileException: 文件不存在或提取失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 提取文件中从偏移量1000开始的512字节
            >>> binary_handler.extract_chunk('source.bin', 'chunk.bin', 1000, 512)
        """
        file_path = Path(file_path)
        output_path = Path(output_path)
        
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        # 确保输出文件的父目录存在
        if create_dirs:
            output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 读取数据块
            data = self.read_chunk(file_path, start, size)
            
            # 写入输出文件
            with open(output_path, 'wb') as f:
                f.write(data)
        except Exception as e:
            raise FileException(f"提取数据块失败: {e}")
        
        self.logger.debug(f"已从{file_path}提取{len(data)}字节到{output_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def insert_chunk(self, file_path: Union[str, Path], data: bytes, offset: int) -> bool:
        """
        在文件的指定位置插入数据（不覆盖现有数据）
        
        Args:
            file_path: 文件路径
            data: 要插入的数据
            offset: 插入位置的偏移量
        
        Returns:
            bool: 插入成功返回True
        
        Raises:
            FileException: 文件不存在或插入失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 在偏移量100处插入数据
            >>> binary_handler.insert_chunk('example.bin', b'\x00\x01\x02', 100)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 读取文件内容
            with open(file_path, 'rb') as f:
                original_data = f.read()
            
            # 插入数据
            if offset > len(original_data):
                # 如果偏移量超过文件大小，先填充零字节
                new_data = original_data + b'\x00' * (offset - len(original_data)) + data
            else:
                new_data = original_data[:offset] + data + original_data[offset:]
            
            # 写回文件
            with open(file_path, 'wb') as f:
                f.write(new_data)
        except Exception as e:
            raise FileException(f"插入数据失败: {e}")
        
        self.logger.debug(f"已在偏移量{offset}处插入{len(data)}字节: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def delete_chunk(self, file_path: Union[str, Path], offset: int, size: int) -> bool:
        """
        删除文件中的一块数据
        
        Args:
            file_path: 文件路径
            offset: 起始偏移量
            size: 要删除的字节数
        
        Returns:
            bool: 删除成功返回True
        
        Raises:
            FileException: 文件不存在或删除失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 删除偏移量100开始的50个字节
            >>> binary_handler.delete_chunk('example.bin', 100, 50)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 读取文件内容
            with open(file_path, 'rb') as f:
                original_data = f.read()
            
            # 删除数据
            if offset >= len(original_data):
                # 如果偏移量超过文件大小，不做任何操作
                return True
            
            end = min(offset + size, len(original_data))
            new_data = original_data[:offset] + original_data[end:]
            
            # 写回文件
            with open(file_path, 'wb') as f:
                f.write(new_data)
        except Exception as e:
            raise FileException(f"删除数据块失败: {e}")
        
        self.logger.debug(f"已删除偏移量{offset}处的{size}字节: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def chunk_iterator(self, file_path: Union[str, Path], chunk_size: int = 1024 * 1024) -> Iterator[bytes]:
        """
        创建文件的数据块迭代器
        
        Args:
            file_path: 文件路径
            chunk_size: 每个数据块的大小，默认为1MB
        
        Returns:
            Iterator[bytes]: 数据块迭代器
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> # 以64KB为单位处理文件
            >>> for chunk in binary_handler.chunk_iterator('example.bin', 64 * 1024):
            >>>     print(f"处理{len(chunk)}字节")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            with open(file_path, 'rb') as f:
                while True:
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    yield chunk
        except Exception as e:
            raise FileException(f"读取数据块失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def is_binary(self, file_path: Union[str, Path], sample_size: int = 8192) -> bool:
        """
        检测文件是否为二进制文件
        
        Args:
            file_path: 文件路径
            sample_size: 用于检测的样本大小，默认为8KB
        
        Returns:
            bool: 如果是二进制文件则返回True，否则返回False
        
        Raises:
            FileException: 文件不存在或检测失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> is_bin = binary_handler.is_binary('example.bin')
            >>> print(f"文件是{'二进制' if is_bin else '文本'}文件")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            # 读取文件样本
            with open(file_path, 'rb') as f:
                sample = f.read(sample_size)
            
            # 检查是否包含空字节或大量非ASCII字符
            # 文本文件通常不包含空字节，且大部分字符是可打印的ASCII字符
            if b'\x00' in sample:  # 包含空字节，很可能是二进制文件
                return True
            
            # 计算非ASCII字符的比例
            non_ascii_count = sum(1 for b in sample if b < 32 and b not in (9, 10, 13))  # 制表符、换行符和回车符除外
            if non_ascii_count / len(sample) > 0.3:  # 如果非ASCII字符比例超过30%，认为是二进制文件
                return True
            
            return False
        except Exception as e:
            raise FileException(f"检测文件类型失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_file_signature(self, file_path: Union[str, Path], size: int = 8) -> bytes:
        """
        获取文件的签名（文件头部的前几个字节）
        
        Args:
            file_path: 文件路径
            size: 要读取的字节数，默认为8
        
        Returns:
            bytes: 文件签名
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> signature = binary_handler.get_file_signature('example.png')
            >>> print(f"文件签名: {signature.hex()}")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            with open(file_path, 'rb') as f:
                signature = f.read(size)
        except Exception as e:
            raise FileException(f"读取文件签名失败: {e}")
        
        return signature
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_file_signature(self, file_path: Union[str, Path], num_bytes: int = 16) -> bytes:
        """
        获取文件的签名（文件头部的字节序列）
        
        Args:
            file_path: 文件路径
            num_bytes: 要读取的字节数，默认为16
            
        Returns:
            bytes: 文件头部的字节序列
            
        Raises:
            FileException: 文件不存在或读取失败时抛出
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
            
        try:
            with open(file_path, 'rb') as f:
                return f.read(num_bytes)
        except Exception as e:
            raise FileException(f"读取文件签名失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def detect_file_type(self, file_path: Union[str, Path]) -> str:
        """
        根据文件签名检测文件类型
        
        Args:
            file_path: 文件路径
        
        Returns:
            str: 检测到的文件类型，如果无法识别则返回'unknown'
        
        Raises:
            FileException: 文件不存在或检测失败时抛出
        
        Examples:
            >>> binary_handler = BinaryFileHandler()
            >>> file_type = binary_handler.detect_file_type('example.jpg')
            >>> print(f"文件类型: {file_type}")
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        # 常见文件类型的签名
        signatures = {
            b'\x89PNG\r\n\x1a\n': 'png',
            b'\xff\xd8\xff': 'jpeg',
            b'GIF87a': 'gif',
            b'GIF89a': 'gif',
            b'PK\x03\x04': 'zip',
            b'\x50\x4b\x03\x04': 'zip',  # ZIP archive
            b'\x50\x4b\x05\x06': 'zip',  # ZIP archive (empty)
            b'\x50\x4b\x07\x08': 'zip',  # ZIP archive (spanned)
            b'Rar!\x1a\x07\x00': 'rar',
            b'Rar!\x1a\x07\x01\x00': 'rar',
            b'\x1f\x8b\x08': 'gzip',
            b'BZh': 'bzip2',
            b'\x42\x5a\x68': 'bzip2',
            b'\x75\x73\x74\x61\x72': 'tar',
            b'\x75\x73\x74\x61\x72\x00\x30\x30': 'tar',
            b'7z\xbc\xaf\x27\x1c': '7z',
            b'\x25\x50\x44\x46': 'pdf',
            b'\x4d\x5a': 'exe',
            b'\x4d\x53\x43\x46': 'cab',
            b'\x49\x53\x63\x28': 'cab',
            b'\x4f\x67\x67\x53': 'ogg',
            b'\x38\x42\x50\x53': 'psd',
            b'\x52\x49\x46\x46': 'wav',
            b'\x49\x44\x33': 'mp3',
            b'\xff\xfb': 'mp3',
            b'\x00\x00\x00\x14\x66\x74\x79\x70': 'mp4',
            b'\x00\x00\x00\x18\x66\x74\x79\x70': 'mp4',
            b'\x00\x00\x00\x20\x66\x74\x79\x70': 'mp4',
            b'\x1a\x45\xdf\xa3': 'mkv',
            b'\x3c\x3f\x78\x6d\x6c': 'xml',
            b'\x3c\x68\x74\x6d\x6c': 'html',
            b'\x3c\x21\x44\x4f\x43\x54\x59\x50\x45': 'html',
            b'\x7b\x5c\x72\x74\x66': 'rtf',
            b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1': 'doc',  # MS Office
            b'\x50\x4b\x03\x04\x14\x00\x06\x00': 'docx',  # MS Office 2007+
            b'\x50\x4b\x03\x04\x14\x00\x08\x08': 'docx',  # MS Office 2007+
            b'\x00\x01\x00\x00\x53\x74\x61\x6e': 'mdb',  # MS Access
            b'\x23\x20\x44\x69\x73\x6b\x20\x44': 'vmdk',  # VMware disk
            b'\x43\x57\x53': 'swf',  # Flash
            b'\x46\x57\x53': 'swf',  # Flash
            b'\x21\x3c\x61\x72\x63\x68\x3e': 'deb',  # Debian package
            b'\xed\xab\xee\xdb': 'rpm',  # RPM package
            b'\x53\x51\x4c\x69\x74\x65': 'sqlite',  # SQLite database
            b'\x4e\x45\x53\x1a': 'nes',  # Nintendo ROM
            b'\x75\x73\x74\x61\x72': 'tar',  # Tar archive
        }
        
        try:
            # 获取文件签名
            signature = self.get_file_signature(file_path, 16)  # 读取前16个字节
            
            # 检查签名
            for sig, file_type in signatures.items():
                if signature.startswith(sig):
                    return file_type
            
            # 如果没有匹配的签名，尝试根据文件扩展名判断
            ext = file_path.suffix.lower()
            if ext:
                return ext[1:]  # 去掉点号
            
            # 无法识别文件类型
            return 'unknown'
        except Exception as e:
            raise FileException(f"检测文件类型失败: {e}")